{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Fluent Python\n",
    "> Notes By zzf 20190527\n",
    "\n",
    "> Reference: ***Fluent Python*** *By Luciano Ramalho*\n\n",
    "## Chapter 1  Python 数据模型\n",
    "> Python 最好的品质之一是一致性。当你使用 Python 工作一会儿后，就会开始理解 Python 语言，并能正确猜测出对你来说全新的语言特征。\n",
    "然而，如果你带着来自其他面向对象语言的经验进入 Python 的世界，会对 len(colleciton) 而不是 collection.len() 写法觉得不适。当你进一步理解这种不适 感背后的原因之后，会发现这个原因，和它所代表的庞大的设计思想，是形成我们通常说 的“Python 风格”（Pythonic）的关键。这种设计思想完全体现在 Python 的数据模型上，而 数据模型所描述的 API，为使用最地道的语言特性来构建你自己的对象提供了工具。\n",
    "\n",
    "### 特殊方法 / magic method / dunder method\n",
    "```py\n",
    "__init__, __len__, __getitem__\n",
    "```\n",
    "\n",
    "#### Exp 1.1 纸牌"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import collections\n",
    "Card = collections.namedtuple('Card', ['rank', 'suit'])\n",
    "class FrenchDeck:    \n",
    "    ranks = [str(n) for n in range(2, 11)] + list('JQKA')    \n",
    "    suits = 'spades diamonds clubs hearts'.split()\n",
    "    def __init__(self):\n",
    "        self._cards = [Card(rank, suit) for suit in self.suits \n",
    "                                        for rank in self.ranks]\n",
    "    def __len__(self):\n",
    "        return len(self._cards)\n",
    "    def __getitem__(self, position):\n",
    "        return self._cards[position]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Notes\n",
    "```py\n",
    "__len__(self) # 外部使用len()调用该类对象时，__len__起作用，进一层用len()调用list对象，由list对象的__len__起作用\n",
    "\n",
    "__getitem__(self) # 外部使用[]调用该类对象时，__getitem__起作用，进一层用[]调用dict对象，由dict对象的__getitem__起作用\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "deck = FrenchDeck()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Notes\n",
    "```py\n",
    "# 由__len__实现\n",
    "len(dect)\n",
    "\n",
    "# 由__getitem__实现\n",
    "deck[0]\n",
    "deck[-1]\n",
    "deck[12::13]\n",
    "\n",
    "for card in deck: \n",
    "    print(card)\n",
    "\n",
    "from random import choice\n",
    "choice(deck) # Card(rank='3', suit='hearts')\n",
    "choice(deck) # Card(rank='K', suit='spades') \n",
    "choice(deck) # Card(rank='2', suit='clubs')\n",
    "\n",
    "\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "suit_values = dict(spades=3, hearts=2, diamonds=1, clubs=0)\n",
    "def spades_high(card):\n",
    "    rank_value = FrenchDeck.ranks.index(card.rank)\n",
    "    return rank_value * len(suit_values) + suit_values[card.suit]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Notes\n",
    "```py\n",
    "FrenchDeck.ranks # ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A']\n",
    "FrenchDeck.ranks.index('A') # 12 -> return the index of the input element\n",
    "rank_value * len(suit_values) + suit_values[card.suit] # get the 'score' of input card\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": "Card(rank='2', suit='clubs')\nCard(rank='2', suit='diamonds')\nCard(rank='2', suit='hearts')\nCard(rank='2', suit='spades')\nCard(rank='3', suit='clubs')\nCard(rank='3', suit='diamonds')\nCard(rank='3', suit='hearts')\nCard(rank='3', suit='spades')\nCard(rank='4', suit='clubs')\nCard(rank='4', suit='diamonds')\nCard(rank='4', suit='hearts')\nCard(rank='4', suit='spades')\nCard(rank='5', suit='clubs')\nCard(rank='5', suit='diamonds')\nCard(rank='5', suit='hearts')\nCard(rank='5', suit='spades')\nCard(rank='6', suit='clubs')\nCard(rank='6', suit='diamonds')\nCard(rank='6', suit='hearts')\nCard(rank='6', suit='spades')\nCard(rank='7', suit='clubs')\nCard(rank='7', suit='diamonds')\nCard(rank='7', suit='hearts')\nCard(rank='7', suit='spades')\nCard(rank='8', suit='clubs')\nCard(rank='8', suit='diamonds')\nCard(rank='8', suit='hearts')\nCard(rank='8', suit='spades')\nCard(rank='9', suit='clubs')\nCard(rank='9', suit='diamonds')\nCard(rank='9', suit='hearts')\nCard(rank='9', suit='spades')\nCard(rank='10', suit='clubs')\nCard(rank='10', suit='diamonds')\nCard(rank='10', suit='hearts')\nCard(rank='10', suit='spades')\nCard(rank='J', suit='clubs')\nCard(rank='J', suit='diamonds')\nCard(rank='J', suit='hearts')\nCard(rank='J', suit='spades')\nCard(rank='Q', suit='clubs')\nCard(rank='Q', suit='diamonds')\nCard(rank='Q', suit='hearts')\nCard(rank='Q', suit='spades')\nCard(rank='K', suit='clubs')\nCard(rank='K', suit='diamonds')\nCard(rank='K', suit='hearts')\nCard(rank='K', suit='spades')\nCard(rank='A', suit='clubs')\nCard(rank='A', suit='diamonds')\nCard(rank='A', suit='hearts')\nCard(rank='A', suit='spades')\n"
    }
   ],
   "source": [
    "for card in sorted(deck, key=spades_high):\n",
    "    print(card)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Notes\n",
    "```py\n",
    "sorted(deck, key=spades_high) # sorted(iterable, key=function) # every element in iterable will apply the function\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "## Chapter 16 Coroutine"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "def simple_coroutine():\n",
    "    print(\"-> Coroutine Started\")\n",
    "    x = yield\n",
    "    print(\"-> Coroutine Received:\", x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": "<generator object simple_coroutine at 0x0000022ED0766228>"
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "my_coro = simple_coroutine()\n",
    "my_coro"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": "0\n-> Coroutine Started\n1\n-> Coroutine Received: None\n"
    },
    {
     "ename": "StopIteration",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mStopIteration\u001b[0m                             Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-7-8426ef1d77e2>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m      2\u001b[0m \u001b[1;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[1;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;36m2\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      3\u001b[0m     \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mi\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 4\u001b[1;33m     \u001b[0mnext\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mmy_coro\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;31mStopIteration\u001b[0m: "
     ]
    }
   ],
   "source": [
    "my_coro = simple_coroutine()\n",
    "for i in range(2):\n",
    "    print(i)\n",
    "    next(my_coro)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": "-> Coroutine Started\n-> Coroutine Received: Testing String\n"
    },
    {
     "ename": "StopIteration",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mStopIteration\u001b[0m                             Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-5-8176beeb58fc>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m      3\u001b[0m \u001b[0mnext\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mmy_coro\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      4\u001b[0m \u001b[1;31m# 上一步运行后协程运行到yield前一句，处于暂停状态，可以调用send()，恢复协程\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 5\u001b[1;33m \u001b[0mmy_coro\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Testing String\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m      6\u001b[0m \u001b[1;31m# 一直运行到下一个yield表达式之前或者终止\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      7\u001b[0m \u001b[1;31m# 这里，控制权流动到协程定义体的末尾，导致生成器像往常一样抛出 StopIteration异常\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mStopIteration\u001b[0m: "
     ]
    }
   ],
   "source": [
    "my_coro = simple_coroutine()\n",
    "# 激活协程\n",
    "next(my_coro)\n",
    "# 上一步运行后协程运行到yield前一句，处于暂停状态，可以调用send()，恢复协程\n",
    "my_coro.send(\"Testing String\")\n",
    "# 一直运行到下一个yield表达式之前或者终止\n",
    "# 这里，控制权流动到协程定义体的末尾，导致生成器像往常一样抛出 StopIteration异常"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'simple_coroutine' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-2-b64a2d26948b>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mmy_coro\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0msimple_coroutine\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m      2\u001b[0m \u001b[1;31m# 未激活协程无法调用send()\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      3\u001b[0m \u001b[0mmy_coro\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Testing String\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mNameError\u001b[0m: name 'simple_coroutine' is not defined"
     ]
    }
   ],
   "source": [
    "my_coro = simple_coroutine()\n",
    "# 未激活协程无法调用send()\n",
    "my_coro.send(\"Testing String\")"
   ]
  },
  {
   "cell_type": "markdown",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "> 最先调用`next(my_coro)`函数这一步通常称为“预激”（prime）协程（即，让协程向前执行到第一个`yield`表达式，准备好作为活跃的协程使用）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    " from inspect import getgeneratorstate \n",
    " \n",
    " def simple_coro2(a):\n",
    "     print('-> Started: a =', a)\n",
    "     b = yield a \n",
    "     print('-> Received: b =', b)\n",
    "     c = yield a + b\n",
    "     print('-> Received: c =', c) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 0,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    my_coro2 = simple_coro2(14)\n",
    "    print(getgeneratorstate(my_coro2))\n",
    "    # 先传出值(1st), 然后暂停，等待为 b 赋值\n",
    "    print(\"yield a:\", next(my_coro2))\n",
    "    print(getgeneratorstate(my_coro2))\n",
    "    # 传入(1st)，运行到第二个yield时传出(2nd)，然后暂停，等待为c赋值\n",
    "    print(\"yield a+b:\", my_coro2.send(28))\n",
    "    # 传入(2nd)，运行，然后协程终止，导致生成器对象抛出 StopIteration异常\n",
    "    print(\"last:\", my_coro2.send(99))\n",
    "except StopIteration as e:\n",
    "    print(e)\n",
    "    print(getgeneratorstate(my_coro2))"
   ]
  },
  {
   "cell_type": "markdown",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": "GEN_CLOSED\n"
    }
   ],
   "source": [
    "> 在赋值语句中，`=`右边的代码在赋值之前执行。因此，对于`b = yield a`这行代码来说，等到客户端代码再激活协程时才会设定`b`的值。这种行为要花点时间才能习惯，不过一定要理解，这样才能弄懂异步编程中`yield`的作用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "from functools import wraps\n",
    "\n",
    "def coroutine(func):\n",
    "    \"\"\"装饰器：向前执行到第一个`yield`表达式，预激`func`\"\"\"        \n",
    "    @wraps(func)\n",
    "    def primer(*args,**kwargs):\n",
    "        gen = func(*args,**kwargs)\n",
    "        next(gen)\n",
    "        return gen\n",
    "    \n",
    "    return primer\n",
    "\n",
    "@coroutine\n",
    "def averager():\n",
    "    total = 0.0\n",
    "    count = 0\n",
    "    average = None\n",
    "    while True:\n",
    "        term = yield average\n",
    "        total += term\n",
    "        count += 1\n",
    "        average = total/count"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": "GEN_SUSPENDED\n1.0\n3.0\n5.0\n"
    }
   ],
   "source": [
    "coro_avg = averager()\n",
    "print(getgeneratorstate(coro_avg))\n",
    "for data in range(1, 11, 4):\n",
    "    print(coro_avg.send(data))"
   ]
  },
  {
   "cell_type": "markdown",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "> 使用 `ield from`句法（参见 16.7 节）调用协程时，会自动预激，因此与示例 16-5 中的`@coroutine`等装饰器不兼容。Python 3.4 标准库里的`asyncio.coroutine`装饰器（第 18 章介绍）不会预激协程，因此能兼容`yield from`句法"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": "unsupported operand type(s) for +=: 'float' and 'str'\nGEN_CLOSED\n"
    },
    {
     "ename": "StopIteration",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mStopIteration\u001b[0m                             Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-6-ff0c55b56bba>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m      5\u001b[0m     \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mgetgeneratorstate\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mcoro_avg\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      6\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 7\u001b[1;33m \u001b[0mcoro_avg\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"String\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;31mStopIteration\u001b[0m: "
     ]
    }
   ],
   "source": [
    "try:\n",
    "    coro_avg.send(\"String\")\n",
    "except TypeError as e:\n",
    "    print(e)\n",
    "    print(getgeneratorstate(coro_avg))\n",
    "\n",
    "coro_avg.send(\"String\")"
   ]
  },
  {
   "cell_type": "markdown",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": "GEN_CLOSED\n"
    }
   ],
   "source": [
    "> 示例 16-7 暗示了终止协程的一种方式：发送某个哨符值，让协程退出。内置的 None 和 Ellipsis 等常量经常用作哨符值。Ellipsis 的优点是，数据流中不太常有这个值。我还见过有人把 StopIteration 类（类本身，而不是实例，也不抛出）作为哨符值；也就是说，是像这样使用的：my_coro.send(StopIteration)。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "from collections import namedtuple\n",
    "\n",
    "\n",
    "data = {\n",
    "    'girls;kg': [40.9, 38.5, 44.3, 42.2, 45.2, 41.7, 44.5, 38.0, 40.6, 44.5],\n",
    "    'girls;m': [1.6, 1.51, 1.4, 1.3, 1.41, 1.39, 1.33, 1.46, 1.45, 1.43],\n",
    "    'boys;kg': [39.0, 40.8, 43.2, 40.8, 43.1, 38.6, 41.4, 40.6, 36.3],\n",
    "    'boys;m': [1.38, 1.5, 1.32, 1.25, 1.37, 1.48, 1.25, 1.49, 1.46],\n",
    "}\n",
    "\n",
    "Result = namedtuple('Result', 'count average')\n",
    "\n",
    "# the subgenerator\n",
    "def averager():\n",
    "    total = 0.0\n",
    "    count = 0\n",
    "    average = None\n",
    "    while True:\n",
    "        term = yield\n",
    "        if term is None:\n",
    "            break\n",
    "        total += term\n",
    "        count += 1\n",
    "        average = total/count\n",
    "    return Result(count, average)\n",
    "\n",
    "# the delegating generator\n",
    "def grouper(results, key):\n",
    "    count = 0\n",
    "    while True:\n",
    "        # 自动处理异常并提取返回值，处理异常后while loop中止\n",
    "        results[key] = yield from averager()\n",
    "        count += 1\n",
    "        print(\"while loop\", count, results, key)\n",
    "\n",
    "# the client code, a.k.a. the caller\n",
    "def main(data):\n",
    "    results = {}\n",
    "    for key, values in data.items():\n",
    "        group = grouper(results, key)\n",
    "        next(group)\n",
    "        for value in values:\n",
    "            group.send(value)\n",
    "        group.send(None)  # important!\n",
    "\n",
    "    # print(results)  # uncomment to debug\n",
    "    report(results)\n",
    "\n",
    "# output report\n",
    "def report(results):\n",
    "    for key, result in sorted(results.items()):\n",
    "        group, unit = key.split(';')\n",
    "        print('{:2} {:5} averaging {:.2f}{}'.format(result.count, group, result.average, unit))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": "while loop 1 {'girls;kg': Result(count=10, average=42.040000000000006)} girls;kg\nwhile loop 1 {'girls;kg': Result(count=10, average=42.040000000000006), 'girls;m': Result(count=10, average=1.4279999999999997)} girls;m\nwhile loop 1 {'girls;kg': Result(count=10, average=42.040000000000006), 'girls;m': Result(count=10, average=1.4279999999999997), 'boys;kg': Result(count=9, average=40.422222222222224)} boys;kg\nwhile loop 1 {'girls;kg': Result(count=10, average=42.040000000000006), 'girls;m': Result(count=10, average=1.4279999999999997), 'boys;kg': Result(count=9, average=40.422222222222224), 'boys;m': Result(count=9, average=1.3888888888888888)} boys;m\n 9 boys  averaging 40.42kg\n 9 boys  averaging 1.39m\n10 girls averaging 42.04kg\n10 girls averaging 1.43m\n"
    }
   ],
   "source": [
    "main(data)"
   ]
  },
  {
   "cell_type": "markdown",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "## Chapter 17  使用`future`处理并发"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os, time, sys, requests\n",
    "\n",
    "POP20_CC = ('CN IN US ID BR PK NG BD RU JP '\n",
    "            'MX PH VN ET EG DE IR TR CD FR').split()\n",
    "\n",
    "BASE_URL = 'http://flupy.org/data/flags'\n",
    "\n",
    "DEST_DIR = 'downloads/'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Flags:\n",
    "    \n",
    "    @staticmethod\n",
    "    def save_flag(img, filename):\n",
    "        path = os.path.join(DEST_DIR, filename)\n",
    "        with open(path, 'wb') as fp:\n",
    "            fp.write(img)\n",
    "    \n",
    "    @staticmethod\n",
    "    def get_flag(cc):\n",
    "        url = '{}/{cc}/{cc}.gif'.format(BASE_URL, cc=cc.lower())\n",
    "        resp = requests.get(url)\n",
    "        return resp.content\n",
    "    \n",
    "    @staticmethod\n",
    "    def show(text):\n",
    "        print(text, end=',')\n",
    "        sys.stdout.flush()\n",
    "    \n",
    "    @classmethod\n",
    "    def download_many(cls, cc_list):\n",
    "        for cc in sorted(cc_list):\n",
    "            image = cls.get_flag(cc)\n",
    "            cls.show(cc)\n",
    "            cls.save_flag(image, cc.lower() + '.gif')\n",
    "\n",
    "        return len(cc_list)\n",
    "    \n",
    "    @classmethod\n",
    "    def main(cls):\n",
    "        t0 = time.time()\n",
    "        count = cls.download_many(POP20_CC)\n",
    "        elapsed = time.time() - t0\n",
    "        msg = '\\n{} flags downloaded in {:.2f}s'\n",
    "        print(msg.format(count, elapsed))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": "BD,BR,CD,CN,DE,EG,ET,FR,ID,IN,IR,JP,MX,NG,PH,PK,RU,TR,US,VN,\n20 flags downloaded in 39.14s\n"
    }
   ],
   "source": [
    "Flags.main()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "from concurrent import futures\n",
    "\n",
    "\n",
    "MAX_WORKERS = 20\n",
    "\n",
    "\n",
    "class Flags_Threadpool(Flags):\n",
    "\n",
    "    @classmethod\n",
    "    def download_one(cls, cc):\n",
    "        image = cls.get_flag(cc)\n",
    "        cls.show(cc)\n",
    "        cls.save_flag(image, cc.lower() + '.gif')\n",
    "        return cc\n",
    "\n",
    "    @classmethod\n",
    "    def download_many(cls, cc_list):\n",
    "        workers = min(MAX_WORKERS, len(cc_list))\n",
    "        with futures.ThreadPoolExecutor(workers) as executor:\n",
    "            res = executor.map(cls.download_one, sorted(cc_list))\n",
    "\n",
    "        return len(list(res))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": "BD,ID,RU,FRDE,,JP,IR,MX,CN,EG,CD,TR,PK,US,ET,BR,VN,NG,IN,PH,\n20 flags downloaded in 6.00s\n"
    }
   ],
   "source": [
    "Flags_Threadpool.main()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "import asyncio\n",
    "import aiohttp\n",
    "\n",
    "class Flags_Asyncio(Flags):\n",
    "\n",
    "    @staticmethod\n",
    "    async def get_flag(session, cc):\n",
    "        url = '{}/{cc}/{cc}.gif'.format(BASE_URL, cc=cc.lower())\n",
    "        async with session.get(url) as resp:\n",
    "            return await resp.read()\n",
    "\n",
    "    @classmethod\n",
    "    async def download_one(cls, session, cc):\n",
    "        image = await cls.get_flag(session, cc)\n",
    "        cls.show(cc)\n",
    "        cls.save_flag(image, cc.lower() + '.gif')\n",
    "        return cc\n",
    "\n",
    "    @classmethod\n",
    "    async def download_many(cls, cc_list):\n",
    "        async with aiohttp.ClientSession() as session:\n",
    "            res = await asyncio.gather(\n",
    "                *[asyncio.create_task(cls.download_one(session, cc))\n",
    "                    for cc in sorted(cc_list)])\n",
    "\n",
    "        return len(res)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": "\n<coroutine object Flags_Asyncio.download_many at 0x00000240334467C8> flags downloaded in 0.00s\nC:\\Users\\Nature\\Miniconda3\\lib\\site-packages\\ipykernel_launcher.py:1: RuntimeWarning: coroutine 'Flags_Asyncio.download_many' was never awaited\n  \"\"\"Entry point for launching an IPython kernel.\n"
    }
   ],
   "source": [
    "Flags_Asyncio.main()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from time import perf_counter, sleep\n",
    "from itertools import cycle\n",
    "\n",
    "def spin(during=10):\n",
    "    start = perf_counter()\n",
    "    for char in cycle(\"|/-\\\\\"):\n",
    "        print(char, flush=True, end=\"\\r\")\n",
    "        sleep(0.07)\n",
    "        if perf_counter() - start >= during:\n",
    "            break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": "|/-\\|/-\\|/-\\|/-\\|/-\\|/-\\|/-\\"
    }
   ],
   "source": [
    "spin(2)"
   ]
  },
  {
   "cell_type": "markdown",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "## Chapter 18 Concurrency with `asyncio`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": "Help on function coroutine in module types:\n\ncoroutine(func)\n    Convert regular generator function to a coroutine.\n\n"
    }
   ],
   "source": [
    "import types\n",
    "\n",
    "help(types.coroutine)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}